/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.geode.distributed.internal.membership.gms;

import org.apache.geode.CancelException;
import org.apache.geode.ForcedDisconnectException;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.distributed.DistributedSystem;
import org.apache.geode.distributed.internal.DistributionManager;
import org.apache.geode.distributed.internal.InternalDistributedSystem;
import org.apache.geode.distributed.internal.membership.InternalDistributedMember;
import org.apache.geode.distributed.internal.membership.MembershipManager;
import org.apache.geode.distributed.internal.membership.gms.interfaces.Manager;
import org.apache.geode.distributed.internal.membership.gms.mgr.GMSMembershipManager;
import org.apache.geode.test.dunit.Wait;
import org.apache.geode.test.dunit.WaitCriterion;

/**
 * This helper class provides access to membership manager information that
 * is not otherwise public
 * @since GemFire 5.5
 */
public class MembershipManagerHelper
{

  /** returns the JGroupMembershipManager for the given distributed system */
  public static MembershipManager getMembershipManager(DistributedSystem sys) {
    InternalDistributedSystem isys = (InternalDistributedSystem)sys;
    DistributionManager dm = (DistributionManager)isys.getDM();
    MembershipManager mgr = dm.getMembershipManager();
    return mgr;
  }
  
  /** act sick.  don't accept new connections and don't process ordered
   * messages.  Use beHealthyMember() to reverse the effects.<p>
   * Note that part of beSickMember's processing is to interrupt and
   * stop any reader threads.  A slow listener in a reader thread should
   * eat this interrupt.
   * @param sys
   */
  public static void beSickMember(DistributedSystem sys) {
    ((Manager)getMembershipManager(sys)).beSick();
  }
  
  /**
   * inhibit failure detection responses.  This can be used in conjunction
   * with beSickMember
   */
  public static void playDead(DistributedSystem sys) {
    try {
      ((Manager)getMembershipManager(sys)).playDead();
    }
    catch (CancelException e) {
      // really dead is as good as playing dead
    }
  }
  
  public static void beHealthyMember(DistributedSystem sys) {
    ((Manager)getMembershipManager(sys)).beHealthy();
  }
  
  /** returns the current coordinator address */
  public static DistributedMember getCoordinator(DistributedSystem sys) {
    return ((Manager)getMembershipManager(sys)).getCoordinator();
  }

  /** returns the current lead member address */
  public static DistributedMember getLeadMember(DistributedSystem sys) {
    return ((Manager)getMembershipManager(sys)).getLeadMember();
  }
  
  /** register a test hook with the manager */
  public static void addTestHook(DistributedSystem sys,
      org.apache.geode.distributed.internal.membership.MembershipTestHook hook) {
    getMembershipManager(sys).registerTestHook(hook);
  }
  
  /** remove a registered test hook */
  public static void removeTestHook(DistributedSystem sys,
      org.apache.geode.distributed.internal.membership.MembershipTestHook hook) {
    getMembershipManager(sys).unregisterTestHook(hook);
  }
  
//  /**
//   * returns the view lock.  Holding this lock will prevent the processing
//   * of new views, and will prevent other threads from being able to access
//   * the view
//   */
//  public static Object getViewLock(DistributedSystem sys) {
//    return getMembershipManager(sys).latestViewLock;
//  }
  
  /** returns true if the given member is shunned */
  public static boolean isShunned(DistributedSystem sys, DistributedMember mbr) {
    return ((Manager)getMembershipManager(sys)).isShunned(mbr);
  }
  
  /** returns true if the given member is a surprise member */
  public static boolean isSurpriseMember(DistributedSystem sys, DistributedMember mbr) {
    return getMembershipManager(sys).isSurpriseMember(mbr);
  }
  
  /**
   * add a member id to the surprise members set, with the given millisecond
   * clock birth time
   */
  public static void addSurpriseMember(DistributedSystem sys,
      DistributedMember mbr, long birthTime) {
    ((Manager)getMembershipManager(sys)).addSurpriseMemberForTesting(mbr, birthTime);
  }

  /**
   * inhibits/enables logging of forced-disconnect messages.
   * For quorum-lost messages this adds expected-exception annotations
   * before and after the messages to make them invisible to greplogs
   */
  public static void inhibitForcedDisconnectLogging(boolean b) {
    GMSMembershipManager.inhibitForcedDisconnectLogging(b);
  }
  
  /**
   * wait for a member to leave the view.  Throws an assertionerror
   * if the timeout period elapses before the member leaves
   */
  public static void waitForMemberDeparture(final DistributedSystem sys, 
      final DistributedMember member, final long timeout) {
    WaitCriterion ev = new WaitCriterion() {
      public boolean done() {
        return !getMembershipManager(sys).getView().contains((InternalDistributedMember)member);
      }
      public String description() {
        String assMsg = "Waited over " + timeout + " ms for " + member 
            + " to depart, but it didn't";
        return assMsg;
      }
    };
    Wait.waitForCriterion(ev, timeout, 200, true);
  }
  
  public static void crashDistributedSystem(final DistributedSystem msys) {
    msys.getLogWriter().info("crashing distributed system: " + msys);
    MembershipManagerHelper.inhibitForcedDisconnectLogging(true);
    MembershipManagerHelper.beSickMember(msys);
    MembershipManagerHelper.playDead(msys);
    GMSMembershipManager mgr = ((GMSMembershipManager)getMembershipManager(msys));
    mgr.forceDisconnect("for testing");
    while (msys.isConnected()) {
      try { Thread.sleep(1000); } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        return;
      }
    }
    MembershipManagerHelper.inhibitForcedDisconnectLogging(false);
  }
  
}
